import asyncore
import os
import imp
import socket
from binascii import hexlify
from time import time
import datetime
import sys
import types

path = os.path.join(os.getcwd(), 'setting.py')
setting = imp.load_source('setting', path)

def put(msg, *args):
	print("%s - %s" %(datetime.datetime.now(), msg)),
	for a in args:
		print(a),
	print"\n",

def pop_cmd(cmds):
	cmd = cmds.pop(0)
	if hasattr(cmd, 'message_name'):
		name = cmd.message_name
	elif hasattr(cmd, 'prepare_func'):
		name = cmd.prepare_func.__name__
	return cmd, name

class Command(object):
	def __init__(self, connection):
		self.connection = connection
	
class SendWithDelay(Command):
	def __init__(self, connection, delay, message_or_func, params):
		Command.__init__(self, connection)
		if type(message_or_func) == types.FunctionType:
			self.prepare_func = message_or_func
		else:
			message = message_or_func
			self.message_name = message.__class__.__name__
			self.payload = message.data.raw[:message.length]
		if params:
			self.params = params
		self.delay = delay			
		
class BlockedReceive(Command):
	def __init__(self, connection, message_name, handler):
		Command.__init__(self, connection)
		self.message_name = message_name
		self.handler = handler
		
def get_next_recv_cmd(cmds):
	idx = 0
	for cmd in cmds:
		if isinstance(cmd, Receive):
			return cmd, idx
		idx += 1		
			
class CloseWithDelay(Command):
	def __init__(self, connection, delay):
		Command.__init__(self, connection)
		self.delay = delay

class Connection(asyncore.dispatcher):
	def __init__(self, conn_id, ip, port, delay_offset=0):
		asyncore.dispatcher.__init__(self)
		self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
		self.server = (ip, port)		
		self.connect(self.server)
		self.cmds = []	
		self.received_messages = []	
		self.receive_handler = {}
		self.send_buffer = ''
		self.prev_send_or_receive_time = time()
		self.is_readable = True
		self.prepare_close = False
		self.delay_offset = 0
		self.conn_id = conn_id
		self.pending_size = 0
		
	def put(self, msg, *args):
		put('[%s]:%s' % (self.conn_id, msg), *args)		

	def handle_connect(self):
		self.put('Connected: %s(%d)' % (self.server[0], self.server[1]))

	def handle_close(self):
		self.close()
		self.put('Connection closed')

	def handle_expt(self):
		self.put('***** EXCEPTION *****')
		self.close() # connection failed, shutdown

	def writable(self):
		if self.cmds:			
			if isinstance(self.cmds[0], SendWithDelay):
				if time() - self.prev_send_or_receive_time > self.cmds[0].delay:
					cmd, cname = pop_cmd(self.cmds)
					print "writable - pop cmd: " + cname
					# resolve message from func
					if hasattr( cmd, 'prepare_func'):
						if hasattr(cmd, 'params'):
							message = cmd.prepare_func(self, **cmd.params)
						else:
							message = cmd.prepare_func(self)
						assert(message)
						cmd.message_name = message.__class__.__name__
						cmd.payload = message.data.raw[:message.length]
					if hasattr(setting, 'pre_send_callback'):
						# print(len(cmd.payload))						
						self.send_buffer, raw = setting.pre_send_callback(self, cmd.payload)
						# print(hexlify(raw))												
					else:
						self.send_buffer = cmd.payload
					self.send_message_name = cmd.message_name
			elif isinstance(self.cmds[0], CloseWithDelay):
				self.is_readable = False				
				if time() - self.prev_send_or_receive_time > self.cmds[0].delay:					
					if not self.prepare_close:
						self.prepare_close = True
						self.is_readable = False						
					else:
						print "**** Close ****"
						self.handle_close()
						return False
		return True
		
	def handle_write(self):
		if self.send_buffer:
			self.put('Writing.. ')
			sent = self.send(self.send_buffer)			
			self.send_buffer = self.send_buffer[sent:]
			self.prev_send_or_receive_time = time()
			if not self.send_buffer:
				if hasattr(setting, 'post_send_callback'):
					setting.post_send_callback()
				self.put( "Done send ", self.send_message_name )
			
	def readable(self):
	 	return self.is_readable

	def handle_read(self):
		self.put('Reading..')
		if hasattr( self, 'receive_buffer'):
			self.receive_buffer += self.recv(setting.max_receive_length)
		else:
			self.receive_buffer = self.recv(setting.max_receive_length)

		count = 1
		while True:
			# pending uncompleted message
			if not self.receive_buffer:
				return
			if not setting.is_message_completed(self.receive_buffer):
				return
			message_size = setting.get_message_size(self.receive_buffer)

			# poll message
			message, self.receive_buffer = setting.poll_message(self.receive_buffer)
			# handle message
			if message:
				self.received_messages.append(message)
				message_name = message.__class__.__name__
				self.put("Received(%d) :" % count, message_name)
				count += 1
				handler = None
				if self.cmds and self.cmds[0].message_name == message_name:
					self.put("Handle blocked receive :", message_name)
					handler = self.cmds[0].handler
					_, cname = pop_cmd(self.cmds)
					print "writable - pop cmd: " + cname
				elif message_name in self.receive_handler:
					handler = self.receive_handler[message_name][1]
				if handler:
					handler(self, message)
				self.prev_send_or_receive_time = time()				
			else:
				return
	 	
	def start_pending_message(self, left_size, raw_size, raw_data):
		print "start pending message ( from %d to %d )" % ( left_size, raw_size )
		self.pending_data = raw_data
		self.pending_size = left_size
		self.pending_full_size = raw_size

	def send_with_delay(self, delay, message_or_func, params = None):
		self.cmds.append(SendWithDelay(self, delay + self.delay_offset, message_or_func, params))
		
	def receive(self, message_name, blocking = True, handler = None):
		if blocking:
			self.cmds.append(BlockedReceive(self, message_name, handler))
		elif handler:
			self.receive_handler[message_name] = (blocking, handler)

	def insert_send_with_delay(self, index, delay, message_or_func, params = None):
		self.cmds.insert(index, SendWithDelay(self, delay + self.delay_offset, message_or_func, params))
		
	def insert_receive(self, index, message_name, blocking = True, handler = None):
		if blocking:
			self.cmds.insert(index, BlockedReceive(self, message_name, handler))
		elif handler:
			self.receive_handler[message_name] = (blocking, handler)

	def close_with_delay(self, delay):
		self.cmds.append(CloseWithDelay(self, delay + self.delay_offset))
